package common

import (
	"errors"
	"math/rand"
	"time"

	"hundsun.com/hsl/hschain/common/keystore"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/merkle"
	"hundsun.com/hsl/hschain/common/util/pb"
	consensustypes "hundsun.com/hsl/hschain/consensus/types"
	"hundsun.com/hsl/hschain/contract/native/reward"
	"hundsun.com/hsl/hschain/protos"
	protoscommon "hundsun.com/hsl/hschain/protos/common"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
)

// InitProducer 生产者初始化：建立到序列号中心的连接
func (bc *BaseClient) InitProducer(conf *consensustypes.ProducerConfig, shouldProceedProducingBlocksFunc func() bool,
	beforeBlockCreatedFunc func() (lastBlockNumber uint64, lastBlockHash protos.Hash),
	afterBlockCreatedFunc func(block *pbledger.Block)) error {
	bc.producerConf = conf
	bc.log.Infof("init. producingInterval: %dms, pendingTxsRetryInterval: %dms, blockExecResultRetryInterval: %dms",
		bc.producerConf.ProducingInterval, bc.producerConf.MinPendingTxFetchInterval, bc.producerConf.MinCheckBlockExecResultFetchInterval)

	bc.shouldProceedProducingBlocksFunc = shouldProceedProducingBlocksFunc
	bc.beforeBlockCreatedFunc = beforeBlockCreatedFunc
	bc.afterBlockCreatedFunc = afterBlockCreatedFunc

	return nil
}

// ProduceBlocks 生产区块
func (bc *BaseClient) ProduceBlocks() {
	var remainedTxs []*pbledger.Transaction
	bc.log.Debugf("ProduceBlocks entry...")
	defer func() {
		//清理未用完的交易
		if len(remainedTxs) > 0 {
			remainedTxHashes := make([]protos.Hash, len(remainedTxs))
			for i := 0; i < len(remainedTxs); i++ {
				remainedTxHashes[i] = remainedTxs[i].Hash()
			}
			bc.log.Infof("UndoPreRemoveTransactions[remained]  starts...")
			removed, err := bc.UndoPreRemoveTransactions(remainedTxHashes)
			if err != nil {
				bc.log.Errorf("UndoPreRemoveTransactions[remained] failed: %s", err.Error())
			} else {
				bc.log.Infof("UndoPreRemoveTransactions[remained]  ends...")
				if bc.log.IsTraceEnabled() {
					for i := range remainedTxHashes {
						bc.log.WithField(logging.LogFieldTxHash, remainedTxHashes[i].GetShortString()).Tracef("UndoPreRemoveTransaction removed:%d cancelTTL:%d", removed[i*2], removed[i*2+1])
					}
				}
			}
		}
		bc.log.Debugf("ProduceBlocks exit")
	}()

	for bc.shouldProceedProducingBlocksFunc() {
		lastBlockNumber, lastBlockHash := bc.beforeBlockCreatedFunc()
		blockNumber := lastBlockNumber + 1
		parentHash := lastBlockHash
		blkLog := bc.log.WithField(logging.LogFieldBlockNumber, blockNumber)

		// 1. 获取候选交易（优先使用上一块未用完的交易，然后才从pending pool中获取）
		var err error
		candidateTxs := remainedTxs
		if len(candidateTxs) > 0 {
			blkLog.Info("use remained txs")
		} else {
			fetchPendingTxStart := time.Now()
			blkLog.Trace("fetching txs from pending pool")
			candidateTxs, err = bc.PreRemoveTransactions()
			if err != nil {
				blkLog.Errorf("failed to fetch pending txs:%s", err.Error())
				time.Sleep(time.Duration(bc.producerConf.MinPendingTxFetchInterval) * time.Millisecond)
				continue
			}
			if len(candidateTxs) == 0 {
				time.Sleep(time.Duration(bc.producerConf.MinPendingTxFetchInterval) * time.Millisecond)
				continue
			}
			blkLog.WithCostField(fetchPendingTxStart).Infof("got %d txs from pending pool", len(candidateTxs))
		}

		// 2. 过滤交易：剔除过期或大小超出限制的交易；按照交易总大小限制选出一批交易
		blockTime := time.Now().UnixNano() / int64(time.Millisecond)
		var selectedTxs []*pbledger.Transaction
		selectedTxs, remainedTxs = bc.filterTxs(candidateTxs, blockTime, blkLog)

		if len(selectedTxs) == 0 {
			blkLog.Warn("candidate txs are all removed")
			continue
		}
		selectedTxHashes := make([]protos.Hash, len(selectedTxs))
		for i, tx := range selectedTxs {
			selectedTxHashes[i] = tx.Hash()
		}

		// 3.　获取验证块执行结果
		checkBlockExecMsg := bc.getCheckBlockExecResult(blockNumber, blkLog)

		// 4. 判断是否还能继续出块（前面进行了多次IO操作，采用dpos等算法时，可能导致超出时间片)
		if !bc.shouldProceedProducingBlocksFunc() {
			blkLog.Infof("UndoPreRemoveTransactions[abort producing]  starts...")
			removed, err := bc.UndoPreRemoveTransactions(selectedTxHashes)
			//removed, err := pendingTxPool.UndoPreRemoveTransactions(selectedTxHashes)
			if err != nil {
				blkLog.Errorf("UndoPreRemoveTransactions[abort producing] failed: %s", err.Error())
			} else {
				blkLog.Infof("UndoPreRemoveTransactions[abort producing]  ends...")
				if blkLog.IsTraceEnabled() {
					for i := range selectedTxHashes {
						blkLog.WithField(logging.LogFieldTxHash, selectedTxHashes[i].GetShortString()).Tracef("UndoPreRemoveTransaction removed:%d cancelTTL:%d", removed[i*2], removed[i*2+1])
					}
				}
			}
			return
		}

		// 5. 出块
		buildBlockStart := time.Now()
		if blkLog.IsTraceEnabled() {
			for _, txHash := range selectedTxHashes {
				blkLog.WithField(logging.LogFieldTxHash, txHash.GetShortString()).Tracef("will be packaged")
			}
		}

		// 判断最小交易费，如果不为0则需要设置添加挖矿交易
		if pb.GetGenesisTransactionPayload().GetFeeLimit().GetMinTxFee() != 0 {
			selectedTxs, err = bc.addMiningTx(selectedTxs)
			if err != nil {
				blkLog.Errorf("add minter tx to selected txs failed, %s", err)
				continue
			}
		}
		newBlock := bc.CreateBlock(blockNumber, blockTime, parentHash, selectedTxs, checkBlockExecMsg)

		blkLog.WithCostField(buildBlockStart).Infof("finish building,tx count: %d", len(selectedTxs))

		// 6.出块后调用的方法
		bc.afterBlockCreatedFunc(newBlock)
		time.Sleep(time.Millisecond * time.Duration(bc.producerConf.ProducingInterval))
	}
}

func (bc *BaseClient) getCheckBlockExecResult(blockNumber uint64, blkLog logging.Log) *protoscommon.BlockExecutedMessage {
	var checkBlockExecMsg *protoscommon.BlockExecutedMessage
	checkBlockNumber, checkBlockEnabled := pb.GetCheckBlockNumber(blockNumber)
	if checkBlockEnabled {
		fetchCheckBlockStart := time.Now()
		for checkBlockExecMsg == nil {
			blkLog.Trace("fetching exec result of check block")
			checkBlockExecMsg = bc.GetBlockExecutedMessage(checkBlockNumber, blkLog)
			time.Sleep(time.Duration(bc.producerConf.MinCheckBlockExecResultFetchInterval) * time.Millisecond)
			if !bc.shouldProceedProducingBlocksFunc() {
				if checkBlockExecMsg == nil {
					blkLog.WithCostField(fetchCheckBlockStart).Warn("failed to get check block exec message")
				}
				return nil
			}
		}
		blkLog.WithCostField(fetchCheckBlockStart).Debugf("got exec result of check block")
	}
	return checkBlockExecMsg
}

// filterTxs　过滤交易：1）剔除掉过期和大小超过限制的交易；2）保证选中的交易总体积不超过限制
func (bc *BaseClient) filterTxs(txs []*pbledger.Transaction, blockTime int64, blkLog logging.Log) (selectedTxs, remainedTxs []*pbledger.Transaction) {
	start := time.Now()
	defer blkLog.ElapsedTime("filterTxs", start)
	var discardTxHashes []protos.Hash
	var totalSizeOfSelectedTxs int
	var totalTxFee int

	maxTxTotalSize := bc.CalcMaxTxsSizeInBlock()
	maxFeeOfBlock := bc.GetCurParam().GetFeeLimit().GetMaxBlockFee()
	if maxFeeOfBlock == 0 {
		maxFeeOfBlock = consensustypes.MaxBlockFee
	}

	for _, tx := range txs {
		var txSize int
		txHash := tx.Hash()
		txLog := blkLog.WithField(logging.LogFieldTxHash, txHash.GetShortString())
		err := pb.ValidateTxExpiration(blockTime, tx)
		if err != nil {
			txLog.Warnf("discarded,reason: %s", err.Error())
			discardTxHashes = append(discardTxHashes, txHash)
			continue
		}

		txSize, err = pb.ValidateTxSize(tx)
		if err != nil {
			txLog.Warnf("discarded, reson: tx size %d is larger than max tx size.", txSize)
			discardTxHashes = append(discardTxHashes, txHash)
			continue
		}

		txFee := int(tx.GetFee())
		maxBlkFee := bc.GetCurParam().GetFeeLimit().GetMaxBlockFee()
		if txFee > int(maxBlkFee) {
			txLog.Warnf("discarded, reson: fee %d is larger than max block fee %d.", txFee, maxBlkFee)
			discardTxHashes = append(discardTxHashes, txHash)
			break
		}

		totalTxFee += txFee
		if totalSizeOfSelectedTxs+txSize+8 <= maxTxTotalSize && totalTxFee <= int(maxFeeOfBlock) {
			totalSizeOfSelectedTxs = totalSizeOfSelectedTxs + txSize + 8
			selectedTxs = append(selectedTxs, tx)
			txLog.Tracef("selected,size=%d, totalSizeOfSelectedTxs=%d", txSize, totalSizeOfSelectedTxs)
		} else {
			remainedTxs = append(remainedTxs, tx)
			txLog.Tracef("remained,size=%d, totalSizeOfSelectedTxs=%d", txSize, totalSizeOfSelectedTxs)
		}
	}
	go bc.handleDiscardTransactions(discardTxHashes, blkLog)
	return
}

// CreateBlock 构建块
func (bc *BaseClient) CreateBlock(blockNumber uint64, blockTime int64, parentHash protos.Hash, txs []*pbledger.Transaction, checkBlockExecMsg *protoscommon.BlockExecutedMessage) *pbledger.Block {
	header := &pbledger.BlockHeader{
		ParentHash:    parentHash,
		Number:        blockNumber,
		Signature:     nil,
		Difficulty:    0,
		BlockTime:     blockTime,
		TxRootHash:    merkle.CalcMerkleRoot(txs),
		BlockSequence: 0,
		Consensus:     nil,
	}
	if checkBlockExecMsg != nil {
		header.CheckBlock = checkBlockExecMsg.BlockNumber
		header.StateHashOfCheckBlock = checkBlockExecMsg.StateHash
	}
	block := &pbledger.Block{
		Header:       header,
		Transactions: txs,
	}
	block.Sign(bc.producerConf.MinerAccount.GetKeyPair())
	return block
}

func (bc *BaseClient) addMiningTx(txs []*pbledger.Transaction) ([]*pbledger.Transaction, error) {
	miningTx := bc.createMiningTx()
	if miningTx == nil {
		return nil, errors.New("MinerTx is empty")
	}

	txs = append([]*pbledger.Transaction{miningTx}, txs...)
	return txs, nil
}

func (bc *BaseClient) createMiningTx() *pbledger.Transaction {
	rewardAmount := bc.GetCurParam().GetReward()
	minTxFee := bc.GetCurParam().GetFeeLimit().GetMinTxFee()
	miningTx := createFormatTx(reward.NativeReward, bc.producerConf.MinerAccount, "", rewardAmount, minTxFee)
	return miningTx
}

func (bc *BaseClient) getMinigTx(block *pbledger.Block) (*pbledger.Transaction, error) {
	if len(block.Transactions) == 0 {
		return nil, errors.New("No transactions in block")
	}
	firstTx := block.Transactions[0]
	//判断交易类型和执行情况
	if firstTx.GetTy() != reward.NativeReward {
		return nil, errors.New("first Transaction is not mining tx")
	}

	return firstTx, nil
}

// GetConf 获取Producer adapter配置信息
func (bc *BaseClient) GetConf() *consensustypes.ProducerConfig {
	return bc.producerConf
}

func (bc *BaseClient) handleDiscardTransactions(txs []protos.Hash, log logging.Log) {
	if len(txs) > 0 {
		log.Infof("removing %d expired txs...", len(txs))
		err := bc.RemoveTransactions(txs)
		if err == nil {
			log.Infof("%d expired txs removed", len(txs))
		} else {
			log.Infof("failed to remove %d expired txs", len(txs))
		}
	}
}

func createFormatTx(execTy string, fromAccount keystore.Account, to string, amount uint64, fee uint64) *pbledger.Transaction {
	rand.Seed(time.Now().Unix())
	tx := &pbledger.Transaction{
		Ty:         execTy,
		From:       string(fromAccount.GetAddress()),
		To:         to,
		Value:      amount,
		Fee:        fee,
		Nonce:      rand.Uint64(),
		ContractId: nil,
		CreateTime: time.Now().UnixNano() / int64(time.Millisecond),
		ChainID:    protos.MainChainID,
		TTL:        15,
		Payload:    nil,
	}

	tx.Sign(fromAccount.GetKeyPair())
	return tx
}
